/// File: VSLabFSICore\environment.fs
/// 
/// Author: Antonio Cisternino (cisterni@di.unipi.it)
/// Author: Davide Morelli (morellid@di.unipi.it)
///   
/// ------------------------------------------------------------
/// Copyright (c) Microsoft Corporation.  All rights reserved.
/// 
/// The use and distribution terms for this software are 
/// contained in the file named license.txt, which can be found 
/// in the root of this distribution.
/// By using this software in any fashion, you are agreeing to 
/// be bound by the terms of this license.
///
/// You must not remove this notice, or any other, from this
/// software.
/// ------------------------------------------------------------

module VSLab.Environment

open System.Windows.Forms
open System.IO
open Microsoft.Win32

open EnvDTE
open EnvDTE80
open EnvDTE90
open Extensibility

// COM Interop to go through the ROT table and fetch the correct instance of devenv.
open System.Runtime.InteropServices
open System.Runtime.InteropServices.ComTypes

[<DllImport("ole32.dll")>]
extern int internal GetRunningObjectTable(uint32 reserved, IRunningObjectTable& pprot)

[<DllImport("ole32.dll")>]
extern int internal CreateBindCtx(uint32 reserved, IBindCtx& pctx);

/// <summary>
/// Connects to the running instance of VS given the PID by inspecting the ROT.
/// </summary>
/// <returns>The DTE COM object to interact with VS</returns>
let internal FetchVSDTE (pid:int) =
  let mutable (prot:IRunningObjectTable) = null 
  let mutable (pmonkenum:IEnumMoniker) = null
  let (monikers:IMoniker[]) =  Array.create 1 null
  let pfeteched = System.IntPtr.Zero
  let mutable (ret:obj) = null
  
  try
    if (GetRunningObjectTable(0u, &prot) <> 0) || (prot = null) then failwith "Error opening the ROT"
    prot.EnumRunning(&pmonkenum)
    pmonkenum.Reset()
    while pmonkenum.Next(1, monikers, pfeteched) = 0 do
      let mutable (insname:string) = null
      let mutable (pctx:IBindCtx) = null
      CreateBindCtx(0u, &pctx) |> ignore
      (monikers.[0]).GetDisplayName(pctx, null, &insname);
      Marshal.ReleaseComObject(pctx) |> ignore
      //printfn "name %s" insname
      if (insname.StartsWith("!VisualStudio.DTE.9") || insname.StartsWith("!VisualStudio.DTE.8")) && insname.EndsWith(":" + pid.ToString()) then
        prot.GetObject(monikers.[0], &ret) |> ignore
  finally
    if prot <> null then Marshal.ReleaseComObject(prot) |> ignore
    if pmonkenum <> null then Marshal.ReleaseComObject(pmonkenum) |> ignore
  (ret :?> EnvDTE.DTE)

open System.Collections.Generic

/// <summary>
/// The Visual Studio PID running this instance of VSLab.
/// It is setup by Viewlets.Init and marshalled through ml_send.
/// </summary>
let mutable internal _vspid = 0

/// <summary>
/// The hidden form used as a proxy between F# Interactive and Visual Studio. 
/// It is used internally by VSLab, should not be called directly.
/// </summary>
let mutable internal _viewletsProxy : Form = null

/// <summary>
/// The list of the active Viewlets. It is used internally and should not be called directly.
/// </summary>
let internal _viewletsList = new List<UserControl>()

/// <summary>
/// The function called to populate the list of hidden viewlets.
/// This function is called when the VSLab menu bar is used.
/// </summary>
let mutable internal populateHiddenFun = fun () -> ()

/// <summary>
/// The PID of the associated instance of Visual Studio  
/// </summary>
let VSPID () = _vspid

/// <summary>
/// The form containing the Viewlets proxy.
/// </summary>
let Proxy () = _viewletsProxy

/// <summary>
/// Return the DTE COM server for the current running instance of Visual Studio.
/// </summary>
let DTE () = FetchVSDTE(_vspid)

/// <summary>
/// Executes VS command, if it fails (usually RPC not ready) it retries up to 5 times.
/// </summary>
let dteExecuteCommand (dte:DTE) cmd arg =
  let rec attempt n =
    if n = 0 then 
      printfn "VSLab internal error: cannot communicate with Visual Studio through DTE"
      false
    else
      try
        dte.ExecuteCommand(cmd, arg)
        true
      with
        _ ->
          printfn "Error sending command to Visual Studio, RPC is likely busy, trying again (%d attempts left)..." n
          attempt (n - 1)
  attempt 5

/// <summary>
/// Equivalent to issue a #r directive to the F# interactive
/// </summary>
/// <param name="path">The path to the assembly</param>
let RequireDll (path:string) =
  dteExecuteCommand (DTE()) "VSLabAddin+Addin.FSIExec" ("#r @\"" + path + "\"") |> ignore

/// <summary>
/// Equivalent to issue a #I directive to the F# interactive
/// </summary>
/// <param name="path">The path to include in the search path</param>
let IncludePath (path:string) =
  dteExecuteCommand (DTE()) "VSLabAddin+Addin.FSIExec" ("#I @\"" + path + "\"") |> ignore

/// <summary>
/// Include the path to managed DirectX if installed
/// </summary>
let IncludeDirectX () =
  let env = System.Environment.ExpandEnvironmentVariables
  let mutable dxbase = env @"%windir%\Microsoft.NET\Managed DirectX"
  if not(System.IO.Directory.Exists(dxbase)) then
    dxbase <- env @"%windir%\Microsoft.NET\DirectX for Managed Code"

  if not(System.IO.Directory.Exists(dxbase)) then
    printfn "Managed DirectX is missing."
  else   
    for d in System.IO.Directory.GetDirectories(dxbase) do
      IncludePath(d)

/// <summary>
/// Read a string value from the VSLab registry key
/// </summary>
/// <param name="v">The name of the value to read</param>
let internal ReadConfigurationString (v:string) =
  use k = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Unipi\VSLab")
  k.GetValue(v, null) :?> string

/// <summary>
/// Return the VSLab installation dir
/// </summary>
let VSLabInstallDir () =
  let s = ReadConfigurationString("VSLabDir")
  if s.EndsWith("\\") then s
  else s + "\\"

/// <summary>
/// Check if the Windows Vista UAC is enabled. On Windows XP it returns false.
/// </summary>
let UACEnabled () =
  use k = Microsoft.Win32.Registry.LocalMachine.OpenSubKey(@"Software\Microsoft\Windows\CurrentVersion\Policies\System")
  if k <> null then
    (k.GetValue("EnableLUA", "0") :?> int) = 1
  else
    false

/// <summary>
/// Run a system command requiring admin privileges on Vista if UAC is enabled,
/// without UAC behaves as a standard process creation.
/// </summary>
/// <param name="cmd">The path to the executable.</param>
/// <param name="args">The command line arguments as a string.</param>
/// <returns>The Process object representing the started process</returns>
let VSRunElevate (cmd:string) (args:string) =
  let p = System.Diagnostics.ProcessStartInfo(cmd, args)
  if (UACEnabled()) then
    p.Verb <- "runas"
  System.Diagnostics.Process.Start(p)

/// <summary>
/// Send an open statement to F# interactive
/// </summary>
/// <param name="s">The namespace to open</param>
let OpenNS(s:string) =
  let dte = DTE()
  dteExecuteCommand dte "VSLabAddin+Addin.FSIExec" ("open " + s) |> ignore

/// <summary>
/// The VSLab directory in the user profile to store packages and user dependant
/// configuration.
/// </summary>
let VSLabUserDataDir = System.Environment.GetFolderPath(System.Environment.SpecialFolder.ApplicationData) + "\\Unipi\\VSLab\\"

if not(Directory.Exists(VSLabUserDataDir)) then
  Directory.CreateDirectory(VSLabUserDataDir) |> ignore

// PInvoke
open System

[<DllImport("user32.dll")>]
extern int SendMessage(IntPtr hWnd, int Msg, IntPtr wParam, IntPtr lParam)  

[<DllImport("user32.dll", CharSet=CharSet.Auto)>]
extern int RegisterWindowMessage(string msg)
